/* 
 * Copyright 2010 Ziya Guven Koylu, Ozge Koylu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.koylu.caffein.converter;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;

public class ConverterManager {	
	
	private static Map<Class<?>, Class<?>> primitiveMap = null;
	private static Map<Integer, Class<?>> typeMapForVirtualClass;
	private static Map<Integer, Map<Class<?>, Class<? extends Converter>>> converterMap;
	
	static{
		primitiveMap = new HashMap<Class<?>, Class<?>>();
		primitiveMap.put(byte.class, Byte.class);
		primitiveMap.put(short.class, Short.class);
		primitiveMap.put(int.class, Integer.class);
		primitiveMap.put(long.class, Long.class);
		primitiveMap.put(float.class, Float.class);
		primitiveMap.put(double.class, Double.class);
		primitiveMap.put(boolean.class, Boolean.class);
		primitiveMap.put(byte[].class, Byte[].class);
		
		typeMapForVirtualClass = new HashMap<Integer, Class<?>>();
		typeMapForVirtualClass.put(Types.CHAR, String.class);
		typeMapForVirtualClass.put(Types.VARCHAR, String.class);
		typeMapForVirtualClass.put(Types.LONGVARCHAR, String.class);
		typeMapForVirtualClass.put(Types.NUMERIC, Integer.class);
		typeMapForVirtualClass.put(Types.DECIMAL, Integer.class);
		typeMapForVirtualClass.put(Types.SMALLINT, Integer.class);
		typeMapForVirtualClass.put(Types.INTEGER, Integer.class);
		typeMapForVirtualClass.put(Types.BIGINT, Integer.class);
		typeMapForVirtualClass.put(Types.REAL, Integer.class);
		typeMapForVirtualClass.put(Types.FLOAT, Integer.class);
		typeMapForVirtualClass.put(Types.DOUBLE, Integer.class);
		typeMapForVirtualClass.put(Types.BIT, Boolean.class);
		typeMapForVirtualClass.put(Types.TINYINT, Integer.class);
		typeMapForVirtualClass.put(Types.BINARY, byte[].class);
		typeMapForVirtualClass.put(Types.VARBINARY, byte[].class);
		typeMapForVirtualClass.put(Types.LONGVARBINARY, byte[].class);
		typeMapForVirtualClass.put(Types.DATE, java.util.Date.class);
		typeMapForVirtualClass.put(Types.TIME, java.util.Date.class);
		typeMapForVirtualClass.put(Types.TIMESTAMP, java.util.Date.class);
		typeMapForVirtualClass.put(Types.BLOB, byte[].class);
		typeMapForVirtualClass.put(Types.CLOB, byte[].class);
		
		addConverter(Types.CHAR, String.class, GenericConverter.class);
		addConverter(Types.VARCHAR, String.class, GenericConverter.class);
		addConverter(Types.LONGVARCHAR, String.class, GenericConverter.class);
		addConverter(Types.BIT, boolean.class, GenericConverter.class);
		addConverter(Types.BINARY, byte[].class, GenericConverter.class);
		addConverter(Types.VARBINARY, byte[].class, GenericConverter.class);
		addConverter(Types.LONGVARBINARY, byte[].class, GenericConverter.class);
		
		addConverter(Types.DATE, java.sql.Date.class, DateConverter.class);
		addConverter(Types.DATE, java.util.Date.class, DateConverter.class);
		addConverter(Types.TIME, java.sql.Time.class, DateConverter.class);
		addConverter(Types.TIME, java.util.Date.class, DateConverter.class);
		addConverter(Types.TIMESTAMP, java.sql.Timestamp.class, DateConverter.class);
		addConverter(Types.TIMESTAMP, java.util.Date.class, DateConverter.class);
		
		addConverter(Types.TINYINT, BigDecimal.class, NumericConverter.class);
		addConverter(Types.TINYINT, short.class, NumericConverter.class);
		addConverter(Types.TINYINT, int.class, NumericConverter.class);
		addConverter(Types.TINYINT, long.class, NumericConverter.class);
		addConverter(Types.TINYINT, float.class, NumericConverter.class);
		addConverter(Types.TINYINT, double.class, NumericConverter.class);
		
		addConverter(Types.NUMERIC, BigDecimal.class, NumericConverter.class);
		addConverter(Types.NUMERIC, short.class, NumericConverter.class);
		addConverter(Types.NUMERIC, int.class, NumericConverter.class);
		addConverter(Types.NUMERIC, long.class, NumericConverter.class);
		addConverter(Types.NUMERIC, float.class, NumericConverter.class);
		addConverter(Types.NUMERIC, double.class, NumericConverter.class);
		
		addConverter(Types.DECIMAL, BigDecimal.class, NumericConverter.class);
		addConverter(Types.DECIMAL, short.class, NumericConverter.class);
		addConverter(Types.DECIMAL, int.class, NumericConverter.class);
		addConverter(Types.DECIMAL, long.class, NumericConverter.class);
		addConverter(Types.DECIMAL, float.class, NumericConverter.class);
		addConverter(Types.DECIMAL, double.class, NumericConverter.class);
		
		addConverter(Types.SMALLINT, BigDecimal.class, NumericConverter.class);
		addConverter(Types.SMALLINT, short.class, NumericConverter.class);
		addConverter(Types.SMALLINT, int.class, NumericConverter.class);
		addConverter(Types.SMALLINT, long.class, NumericConverter.class);
		addConverter(Types.SMALLINT, float.class, NumericConverter.class);
		addConverter(Types.SMALLINT, double.class, NumericConverter.class);
		
		addConverter(Types.INTEGER, BigDecimal.class, NumericConverter.class);
		addConverter(Types.INTEGER, short.class, NumericConverter.class);
		addConverter(Types.INTEGER, int.class, NumericConverter.class);
		addConverter(Types.INTEGER, long.class, NumericConverter.class);
		addConverter(Types.INTEGER, float.class, NumericConverter.class);
		addConverter(Types.INTEGER, double.class, NumericConverter.class);
		
		addConverter(Types.BIGINT, BigDecimal.class, NumericConverter.class);
		addConverter(Types.BIGINT, short.class, NumericConverter.class);
		addConverter(Types.BIGINT, int.class, NumericConverter.class);
		addConverter(Types.BIGINT, long.class, NumericConverter.class);
		addConverter(Types.BIGINT, float.class, NumericConverter.class);
		addConverter(Types.BIGINT, double.class, NumericConverter.class);
		
		addConverter(Types.REAL, BigDecimal.class, NumericConverter.class);
		addConverter(Types.REAL, short.class, NumericConverter.class);
		addConverter(Types.REAL, int.class, NumericConverter.class);
		addConverter(Types.REAL, long.class, NumericConverter.class);
		addConverter(Types.REAL, float.class, NumericConverter.class);
		addConverter(Types.REAL, double.class, NumericConverter.class);
		
		addConverter(Types.FLOAT, BigDecimal.class, NumericConverter.class);
		addConverter(Types.FLOAT, short.class, NumericConverter.class);
		addConverter(Types.FLOAT, int.class, NumericConverter.class);
		addConverter(Types.FLOAT, long.class, NumericConverter.class);
		addConverter(Types.FLOAT, float.class, NumericConverter.class);
		addConverter(Types.FLOAT, double.class, NumericConverter.class);
		
		addConverter(Types.DOUBLE, BigDecimal.class, NumericConverter.class);
		addConverter(Types.DOUBLE, short.class, NumericConverter.class);
		addConverter(Types.DOUBLE, int.class, NumericConverter.class);
		addConverter(Types.DOUBLE, long.class, NumericConverter.class);
		addConverter(Types.DOUBLE, float.class, NumericConverter.class);
		addConverter(Types.DOUBLE, double.class, NumericConverter.class);
		
		addConverter(Types.CLOB, byte[].class, ClobConverter.class);
		addConverter(Types.BLOB, byte[].class, BlobConverter.class);
	}
	
	public static void addConverter(int jdbcType, Class<?> fieldType, Class<? extends Converter> converter){
		if(converterMap == null){
			converterMap = new HashMap<Integer, Map<Class<?>,Class<? extends Converter>>>();
		}
		Map<Class<?>, Class<? extends Converter>> map = converterMap.get(jdbcType);
		if(map == null){
			map = new HashMap<Class<?>, Class<? extends Converter>>();
			converterMap.put(jdbcType, map);
		}
		map.put(primitiveMap.get(fieldType)==null?fieldType:primitiveMap.get(fieldType), converter);
	}
	
	public static Class<? extends Converter> getConverter(int jdbcType, Class<?> fieldType) throws Exception{
		Map<Class<?>, Class<? extends Converter>> map = converterMap.get(jdbcType);
		if(map != null){
			fieldType = primitiveMap.get(fieldType)==null?fieldType:primitiveMap.get(fieldType);
			if(map.containsKey(fieldType)){
				return map.get(fieldType);	
			}
		}
		return null;
	}
	
	public static Converter getConverterObject(int jdbcType, Class<?> fieldType) throws Exception{
		Class<? extends Converter> converterClass = null;
		if(fieldType.isEnum()){
			if( jdbcType == Types.CHAR || jdbcType == Types.VARCHAR || jdbcType == Types.LONGNVARCHAR){//TODO:map or static values...
				converterClass = EnumConverter.class;				
			}
		}else{
			 converterClass = getConverter(jdbcType, fieldType);
		}
		Converter converter = null;
		if(converterClass != null){
			converter = converterClass.newInstance();
			converter.setJdbcType(jdbcType);
			converter.setFieldType(fieldType);
		}
		return converter;
	}
	
	public static Map<Class<?>, Class<?>> getPrimitiveMap() {
		return primitiveMap;
	}
	
	public static Map<Integer, Class<?>> getTypeMapForVirtualClass() {
		return typeMapForVirtualClass;
	}
	
	public static Map<Integer, Map<Class<?>, Class<? extends Converter>>> getConverterMap() {
		return converterMap;
	}
	
	public static String getJdbcTypeName(int value) throws Exception {
		Field[] fields = Types.class.getFields();
		for (Field field : fields) {
			if (field.getInt(null) == value) {
				return field.getName();
			}
		}
		return null;
	}
	
	public static Integer getJdbcTypeValue(String name) throws Exception {
		Field[] fields = Types.class.getFields();
		for (Field field : fields) {
			if (field.getName().equals(name)) {
				return field.getInt(null);
			}
		}
		return null;
	}
}